/*
 * This file is part of FinVis.
 *
 * FinVis is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FinVis is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with FinVis.  If not, see <http://www.gnu.org/licenses/>
 *
 * @author Stephen Rudolph
 */

#include <QDate>
#include <QtGui>
#include <QComboBox>
#include <QLabel>
#include <QSpinBox>
#include <QPushButton>
#include <QLineEdit>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QMessageBox>

#include <cmath>
#include <limits>
#include <vector>

#include "Common.h"
#include "FinInvestment.h"
#include "FinInvestmentWidget.h"
#include "FinPortfolio.h"
#include "FinRecorder.h"

using namespace std;

extern FinPortfolio scenarioPortfolio;
extern FinRecorder recorder;
extern std::vector<InvestmentOption> investmentOptionVector;
extern double **covarianceArray;

/**
 * Constructor
 */
FinInvestmentWidget::FinInvestmentWidget(QWidget *parent, Qt::WindowFlags flags)
    : QDockWidget(parent, flags)
    , m_widgetSize(QSize(RIGHT_DOCK_WIDTH, MINIMUM_SIZE))
    , m_loading(false)
    , m_investmentPeriod(new QComboBox)
    , m_investmentPeriodLabel(new QLabel(tr("&Duration (years):")))
    , m_investmentType(new QComboBox)
    , m_investmentTypeLabel(new QLabel(tr("&Type:")))
    , m_principalSpin(new QSpinBox)
    , m_principalLabel(new QLabel(tr("&Principal:")))
    , m_maxPrincipalButton(new QPushButton(tr("Max")))
    , m_startYearSpin(new QSpinBox)
    , m_startYearLabel(new QLabel(tr("&Start Year:")))
    , m_endYearSpin(new QSpinBox)
    , m_endYearLabel(new QLabel(tr("&End Year:")))
    , m_interestEdit(new QLineEdit)
    , m_interestLabel(new QLabel(tr("&Interest (APR) %:")))
    , m_riskEdit(new QLineEdit)
    , m_riskLabel(new QLabel(tr("Standard &Deviation (") +
                             QString::fromUtf8("σ") + tr(") %:")))
    , m_investment(new FinInvestment())
    , m_liquidAssets(NULL)
    , m_currentPeriodIndex(-1)
    , m_currentPrincipal(-1)
    , m_currentStartYear(-1)
    , m_currentTypeIndex(-1)
{
    this->setWindowTitle(tr("Modify Selected Investment"));

    //Control state
    m_investmentPeriod->setEnabled(false);
    m_investmentType->setEnabled(false);
    m_principalSpin->setEnabled(false);
    m_maxPrincipalButton->setEnabled(false);
    m_startYearSpin->setEnabled(false);
    m_endYearSpin->setEnabled(false);
    m_interestEdit->setEnabled(false);
    m_riskEdit->setEnabled(false);

    //Buddies
    m_investmentPeriodLabel->setBuddy(m_investmentPeriod);
    m_investmentTypeLabel->setBuddy(m_investmentType);
    m_principalLabel->setBuddy(m_principalSpin);
    m_startYearLabel->setBuddy(m_startYearSpin);
    m_endYearLabel->setBuddy(m_endYearSpin);
    m_interestLabel->setBuddy(m_interestEdit);
    m_riskLabel->setBuddy(m_riskEdit);

    //Spin setings
    m_principalSpin->setRange(0, std::numeric_limits<int>::max());
    m_principalSpin->setPrefix(tr("$"));
    m_principalSpin->setSingleStep(50);
    m_startYearSpin->setRange(START_YEAR, START_YEAR + MAX_YEARS - 1);
    m_endYearSpin->setRange(START_YEAR + 1, START_YEAR + MAX_YEARS);

    //List initialization
    for (vector<InvestmentOption>::iterator ioi =
         investmentOptionVector.begin();
        ioi != investmentOptionVector.end(); ioi++)
        m_investmentType->addItem(tr(ioi->name));

    for (unsigned int i = 0; i < investmentOptionVector.size(); i++)
        m_investmentPeriod->addItem(
                QString::number(investmentOptionVector.at(i).durationYears));

    //Layouts
    QHBoxLayout *investmentPeriodBox = new QHBoxLayout;
    investmentPeriodBox->addWidget(m_investmentPeriodLabel);
    investmentPeriodBox->addStretch();
    investmentPeriodBox->addWidget(m_investmentPeriod);

    QHBoxLayout *investmentTypeBox = new QHBoxLayout;
    investmentTypeBox->addWidget(m_investmentTypeLabel);
    investmentTypeBox->addStretch();
    investmentTypeBox->addWidget(m_investmentType);

    QHBoxLayout *principalBox = new QHBoxLayout;
    principalBox->addWidget(m_principalLabel);
    principalBox->addStretch();
    principalBox->addWidget(m_principalSpin);
    principalBox->addStretch();
    principalBox->addWidget(m_maxPrincipalButton);

    QHBoxLayout *startYearBox = new QHBoxLayout;
    startYearBox->addWidget(m_startYearLabel);
    startYearBox->addStretch();
    startYearBox->addWidget(m_startYearSpin);

    QHBoxLayout *endYearBox = new QHBoxLayout;
    endYearBox->addWidget(m_endYearLabel);
    endYearBox->addStretch();
    endYearBox->addWidget(m_endYearSpin);

    QHBoxLayout *interestBox = new QHBoxLayout;
    interestBox->addWidget(m_interestLabel);
    interestBox->addStretch();
    interestBox->addWidget(m_interestEdit);

    QHBoxLayout *riskBox = new QHBoxLayout;
    riskBox->addWidget(m_riskLabel);
    riskBox->addStretch();
    riskBox->addWidget(m_riskEdit);

    QVBoxLayout *mainLayout = new QVBoxLayout;
    mainLayout->addLayout(investmentPeriodBox);
    mainLayout->addLayout(investmentTypeBox);
    mainLayout->addLayout(principalBox);
    //mainLayout->addLayout(startYearBox);
    //mainLayout->addLayout(endYearBox);
    mainLayout->addLayout(interestBox);
    mainLayout->addLayout(riskBox);

    QWidget *investmentWidget = new QWidget;
    investmentWidget->setLayout(mainLayout);
    this->setWidget(investmentWidget);
    this->m_widgetSize.setWidth(investmentWidget->sizeHint().width());
    this->m_widgetSize.setHeight(investmentWidget->sizeHint().height());

    investmentWidget->setFocusPolicy(Qt::StrongFocus);

    //Connections
    connect(m_principalSpin, SIGNAL(valueChanged(int)),
            this, SLOT(principalChanged(int)));
    connect(m_startYearSpin, SIGNAL(valueChanged(int)),
            this, SLOT(startYearChanged(int)));
    connect(m_investmentPeriod, SIGNAL(currentIndexChanged(const QString&)),
            this, SLOT(investmentPeriodChanged(const QString&)));
    connect(m_investmentType, SIGNAL(currentIndexChanged(const QString&)),
            this, SLOT(investmentTypeChanged(const QString&)));
    connect(&scenarioPortfolio, SIGNAL(selectionChanged()),
            this, SLOT(selectedInvestmentChanged()));
    connect(&scenarioPortfolio, SIGNAL(portfolioDataChanged()),
            this, SLOT(selectedInvestmentChanged()));
    connect(m_maxPrincipalButton, SIGNAL(pressed()),
            this, SLOT(maxInvestmentRequested()));

    // In case there is an existing investment selected, change to it.
    this->selectedInvestmentChanged();
}

QSize FinInvestmentWidget::minimumSizeHint() const
{
    return QSize(MINIMUM_SIZE, MINIMUM_SIZE);
}

QSize FinInvestmentWidget::sizeHint() const
{
    return m_widgetSize;
}

void FinInvestmentWidget::enableButtons()
{
    m_startYearSpin->setEnabled(true);
    m_principalSpin->setEnabled(true);
    m_investmentType->setEnabled(true);
    m_maxPrincipalButton->setEnabled(true);
}

void FinInvestmentWidget::disableButtons()
{
    m_startYearSpin->setEnabled(false);
    m_principalSpin->setEnabled(false);
    m_investmentType->setEnabled(false);
    m_maxPrincipalButton->setEnabled(false);
}

void FinInvestmentWidget::modifyInvestment()
{
    if (!m_loading)
    {
        int principal = m_principalSpin->value();
        FinInvestment proposed = *m_investment;
        proposed.setTitle(m_investmentType->currentText());
        proposed.setPrincipal(principal);
        proposed.setStartDate(QDate(m_startYearSpin->value(), 1, 1));
        proposed.setInvestmentOptionVectorIndex(
                m_investmentType->currentIndex());

        if (scenarioPortfolio.changingSelectedInvestmentRequiresDebt(proposed))
        {
            m_loading = true;

            QMessageBox::warning(this, tr("Debt Alert"),
                                 tr("You do not have the available funds ") +
                                 tr("(marked by the purple guideline) for ") +
                                 tr("this change.\nThis may be caused by a ") +
                                 tr("future investment requiring more than ") +
                                 tr("would be available after this change."),
                                 QMessageBox::Ok, QMessageBox::Ok);

            m_investmentPeriod->setCurrentIndex(m_currentPeriodIndex);
            m_principalSpin->setValue(m_currentPrincipal);
            m_startYearSpin->setValue(m_currentStartYear);
            m_endYearSpin->setValue(m_currentStartYear +
                                  m_investmentPeriod->currentText().toInt());
            m_investmentType->setCurrentIndex(m_currentTypeIndex);

            m_loading = false;
        }
        else
        {
            if (m_currentPrincipal != m_principalSpin->value())
            {
                recorder.recordAction("SetPrincipal", m_principalSpin->value());
            }

            if (m_currentStartYear != m_startYearSpin->value())
            {
                recorder.recordAction("SetStartYear", m_startYearSpin->value());
            }

            if (m_currentTypeIndex != m_investmentType->currentIndex())
            {
                recorder.recordAction("SetType", m_investmentType->currentIndex());
            }

            m_currentPeriodIndex = m_investmentPeriod->currentIndex();
            m_currentPrincipal = m_principalSpin->value();
            m_currentStartYear = m_startYearSpin->value();
            m_currentTypeIndex = m_investmentType->currentIndex();
            m_investment = new FinInvestment(proposed);

            emit investmentChanged(proposed);
        }
    }
}

void FinInvestmentWidget::principalChanged(int i)
{
    this->modifyInvestment();
}

void FinInvestmentWidget::startYearChanged(int i)
{
    m_endYearSpin->setValue(i + m_investmentPeriod->currentText().toInt());
    this->modifyInvestment();
}

void FinInvestmentWidget::investmentPeriodChanged(const QString &text)
{
    m_endYearSpin->setValue(m_startYearSpin->value() + text.toInt());

    m_interestEdit->setText(QString::number(
            investmentOptionVector.at(
                    m_investmentType->currentIndex()).annualPercentageReturn));
    m_riskEdit->setText(QString::number(
            investmentOptionVector.at(
                    m_investmentType->currentIndex()).standardDeviation));

    this->modifyInvestment();
}

void FinInvestmentWidget::investmentTypeChanged(const QString &text)
{
    m_interestEdit->setText(QString::number(investmentOptionVector.at(
            m_investmentType->currentIndex()).annualPercentageReturn));
    m_riskEdit->setText(QString::number(investmentOptionVector.at(
            m_investmentType->currentIndex()).standardDeviation));

    this->modifyInvestment();
}

void FinInvestmentWidget::selectedInvestmentChanged()
{
    this->reset();

    if (scenarioPortfolio.investmentCount() > 0 && !m_loading &&
        !(scenarioPortfolio.selectedInvestment() == NULL))
    {
        this->enableButtons();

        m_loading = true;
        m_principalSpin->setRange(std::numeric_limits<int>::min(),
                                  std::numeric_limits<int>::max());
        m_startYearSpin->setRange(START_YEAR, START_YEAR + MAX_YEARS - 1);
        m_endYearSpin->setRange(START_YEAR + 1, START_YEAR + MAX_YEARS);

        m_investment = scenarioPortfolio.selectedInvestment();

        m_principalSpin->setValue(m_investment->principal());
        m_startYearSpin->setValue(scenarioPortfolio.startYearForInvestment(
                scenarioPortfolio.selectedID()));
        m_endYearSpin->setValue(scenarioPortfolio.startYearForInvestment(
                scenarioPortfolio.selectedID()) + investmentOptionVector.at(
                    m_investment->investmentOptionVectorIndex()).durationYears);
        m_interestEdit->setText(QString::number(investmentOptionVector.at(
                m_investment->investmentOptionVectorIndex()).
                                                annualPercentageReturn));
        m_riskEdit->setText(QString::number(investmentOptionVector.at(
                m_investment->investmentOptionVectorIndex()).
                                            standardDeviation));

        m_investmentPeriod->setCurrentIndex(
                m_investment->investmentOptionVectorIndex());
        m_investmentType->setCurrentIndex(
                m_investment->investmentOptionVectorIndex());

        m_principalSpin->setRange(0, std::numeric_limits<int>::max());
        m_startYearSpin->setRange(START_YEAR, START_YEAR + MAX_YEARS - 1);
        m_endYearSpin->setRange(START_YEAR + 1, START_YEAR + MAX_YEARS);

        m_currentPeriodIndex = m_investmentPeriod->currentIndex();
        m_currentPrincipal = m_principalSpin->value();
        m_currentStartYear = m_startYearSpin->value();
        m_currentTypeIndex = m_investmentType->currentIndex();
    }
    else
    {
        this->disableButtons();
    }

    m_loading = false;
}

/**
 * The user has requested that the investment principal be set to the maximum
 * allowable amount
 */
void FinInvestmentWidget::maxInvestmentRequested()
{
    int yearOffset = m_startYearSpin->value() - START_YEAR;
    int duration = m_endYearSpin->value() - m_startYearSpin->value();

    int maxInvestment = scenarioPortfolio.liquidAssets(yearOffset) +
                        scenarioPortfolio.principalForInvestment(
                                scenarioPortfolio.selectedID());

    for (int i = yearOffset + 1; i < yearOffset + duration; i++)
    {
        if (scenarioPortfolio.liquidAssets(i) +
           scenarioPortfolio.principalForInvestment(
                   scenarioPortfolio.selectedID()) < maxInvestment)
            maxInvestment = scenarioPortfolio.liquidAssets(i) +
                            scenarioPortfolio.principalForInvestment(
                                    scenarioPortfolio.selectedID());
    }

    m_principalSpin->setValue(maxInvestment);
}

void FinInvestmentWidget::reset()
{
    m_currentPeriodIndex = 0;
    m_currentPrincipal = 0;
    m_currentStartYear = START_YEAR;
    m_currentTypeIndex = 0;
}

